<template>
  <div ref="theWrap" class="vue-tabulation" :class="gridClass">
    <div ref="headerWrap" class="vue-tabulation__header-wrapper">
      <!-- 主内容 -->
      <table ref="header" :style="gridHeaderStyle" class="vue-tabulation__header">
        <colgroup>
          <col
            v-for="(col, index) in fixedNormalizeColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
          <col v-if="hasScrollableY" :width="gutterWidth + 'px'" />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(item, index) in fixedNormalizeColumns"
              :data-prop="item.prop"
              ref="headerCell"
              :key="item.prop"
              :class="[computeCellClass(item)]"
            >
              <template v-if="item.headerCellComp">
                <component
                  :is="item.headerCellComp"
                  :label="item.label"
                  :index="index"
                  :prop="item.prop"
                  :column="item"
                  :sort="sort"
                  v-on="$listeners"
                ></component>
              </template>
              <template v-else>
                {{ item.label }}
              </template>
            </th>
            <th v-if="hasScrollableY" class="vue-tabulation__gutter"></th>
          </tr>
        </thead>
      </table>
      <!-- header 左固定列 -->
      <table
        v-if="fixedLeftColumns.length"
        ref="header-fixed-left"
        class="vue-tabulation__header--fixed-left"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedLeftColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(item, index) in fixedLeftColumns"
              :data-prop="item.prop"
              :key="item.prop"
              :class="[computeCellClass(item)]"
            >
              <template v-if="item.headerCellComp">
                <component
                  :is="item.headerCellComp"
                  :label="item.label"
                  :index="index"
                  :prop="item.prop"
                  :column="item"
                  :sort="sort"
                  v-on="$listeners"
                ></component>
              </template>
              <template v-else>
                {{ item.label }}
              </template>
            </th>
          </tr>
        </thead>
      </table>
      <!-- header 右固定列 -->
      <table
        v-if="fixedRightColumns.length"
        ref="header-fixed-right"
        class="vue-tabulation__header--fixed-right"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedRightColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
          <col v-if="hasScrollableY" :width="gutterWidth + 'px'" />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(item, index) in fixedRightColumns"
              :key="item.prop"
              :data-prop="item.prop"
              :class="[computeCellClass(item)]"
            >
              <template v-if="item.headerCellComp">
                <component
                  :is="item.headerCellComp"
                  :label="item.label"
                  :index="index"
                  :prop="item.prop"
                  :column="item"
                  :sort="sort"
                  v-on="$listeners"
                ></component>
              </template>
              <template v-else>
                {{ item.label }}
              </template>
            </th>
            <th v-if="hasScrollableY" class="vue-tabulation__gutter"></th>
          </tr>
        </thead>
      </table>
    </div>
    <!-- end -->
    <div
      ref="bodyWrap"
      class="vue-tabulation__body-wrapper"
      :style="bodyWrapStyle"
      @scroll="debounceScroll"
    >
      <div
        v-if="!normalizeDataSource.length"
        class="vue-tabulation__no-data"
        :style="{ width: totalColWidth + 'px' }"
      >
        暂无数据
      </div>
      <!-- 主内容区域 -->
      <table ref="body" class="vue-tabulation__body">
        <colgroup>
          <col
            v-for="(col, index) in fixedNormalizeColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
        </colgroup>
        <tbody>
          <tr :style="{ height: bodyTopPadding + 'px' }"></tr>
          <tr
            v-for="data in renderDataSource"
            :key="data[keyProp] || data['vue-tabulation_inner_index']"
            class="vue-tabulation-body-tr"
            :class="[
              data['vue-tabulation-checkbox'] ? 'vue-tabulation-body-tr--checked' : '',
              rowClassName && rowClassName(data, data['vue-tabulation_inner_index']),
            ]"
            @click="handleRowClick(data, $event)"
            @contextmenu="handleContextmenu(data, $event)"
          >
            <td
              ref="bodyCell"
              v-for="column in fixedNormalizeColumns"
              :key="column.prop"
              :data-prop="column.prop"
              :class="[computeCellClass(column)]"
            >
              <template v-if="column.bodyCellComp">
                <div class="cell">
                  <component
                    :is="column.bodyCellComp"
                    :formatter="column.bodyFormatter"
                    :value="data[column.prop]"
                    :prop="column.prop"
                    :index="data['vue-tabulation_inner_index']"
                    :column="column"
                    :data="column.data"
                    :row="data"
                    v-on="$listeners"
                  ></component>
                </div>
              </template>
              <template v-else>
                <div class="cell" :title="data[column.prop]">
                  {{
                    column.bodyFormatter
                      ? column.bodyFormatter(
                          data[column.prop],
                          data,
                          column,
                          data['vue-tabulation_inner_index'],
                        )
                      : data[column.prop]
                  }}
                </div>
              </template>
            </td>
          </tr>
          <tr :style="{ height: bodyBottomPadding + 'px' }"></tr>
        </tbody>
      </table>
      <!-- 左固定 -->
      <table
        v-if="fixedLeftColumns.length"
        ref="body-fixed-left"
        class="vue-tabulation__body--fixed-left"
        :style="{ transform: `translateX(${this.gridBodyScrollLeft}px)` }"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedLeftColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
        </colgroup>
        <tbody>
          <tr :style="{ height: bodyTopPadding + 'px' }"></tr>
          <tr
            v-for="data in renderDataSource"
            :key="data['vue-tabulation_inner_index']"
            class="vue-tabulation-body-tr"
            :class="[
              data['vue-tabulation-checkbox'] ? 'vue-tabulation-body-tr--checked' : '',
              rowClassName && rowClassName(data, data['vue-tabulation_inner_index']),
            ]"
            @click="handleRowClick(data, $event)"
            @contextmenu="handleContextmenu(data, $event)"
          >
            <td
              v-for="column in fixedLeftColumns"
              :key="column.prop"
              :data-prop="column.prop"
              :class="[computeCellClass(column)]"
            >
              <template v-if="column.bodyCellComp">
                <div class="cell">
                  <component
                    :is="column.bodyCellComp"
                    :formatter="column.bodyFormatter"
                    :value="data[column.prop]"
                    :prop="column.prop"
                    :index="data['vue-tabulation-index'] - 1"
                    :column="column"
                    :data="column.data"
                    :row="data"
                    v-on="$listeners"
                  ></component>
                </div>
              </template>
              <template v-else>
                <div
                  class="cell"
                  :title="
                    column.bodyFormatter
                      ? column.bodyFormatter(
                          data[column.prop],
                          data,
                          column,
                          data['vue-tabulation_inner_index'],
                        )
                      : data[column.prop]
                  "
                >
                  {{
                    column.bodyFormatter
                      ? column.bodyFormatter(
                          data[column.prop],
                          data,
                          column,
                          data['vue-tabulation_inner_index'],
                        )
                      : data[column.prop]
                  }}
                </div>
              </template>
            </td>
          </tr>
          <tr :style="{ height: bodyBottomPadding + 'px' }"></tr>
        </tbody>
      </table>
      <!-- 右固定 -->
      <table
        v-if="fixedRightColumns.length"
        ref="body-fixed-right"
        class="vue-tabulation__body--fixed-right"
        :style="{ transform: `translateX(${this.gridBodyScrollLeft - 1}px)` }"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedRightColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
        </colgroup>
        <tbody>
          <tr :style="{ height: bodyTopPadding + 'px' }"></tr>
          <tr
            v-for="data in renderDataSource"
            :key="data['vue-tabulation_inner_index']"
            class="vue-tabulation-body-tr"
            :class="[
              data['vue-tabulation-checkbox'] ? 'vue-tabulation-body-tr--checked' : '',
              rowClassName && rowClassName(data, data['vue-tabulation_inner_index']),
            ]"
            @click="handleRowClick(data, $event)"
            @contextmenu="handleContextmenu(data, $event)"
          >
            <td
              v-for="column in fixedRightColumns"
              :key="column.prop"
              :data-prop="column.prop"
              :class="[computeCellClass(column)]"
            >
              <template v-if="column.bodyCellComp">
                <div class="cell">
                  <component
                    :is="column.bodyCellComp"
                    :formatter="column.bodyFormatter"
                    :value="data[column.prop]"
                    :prop="column.prop"
                    :index="data['vue-tabulation-index'] - 1"
                    :column="column"
                    :data="column.data"
                    :row="data"
                    v-on="$listeners"
                  ></component>
                </div>
              </template>
              <template v-else>
                <div
                  class="cell"
                  :title="
                    column.bodyFormatter
                      ? column.bodyFormatter(
                          data[column.prop],
                          data,
                          column,
                          data['vue-tabulation_inner_index'],
                        )
                      : data[column.prop]
                  "
                >
                  {{
                    column.bodyFormatter
                      ? column.bodyFormatter(
                          data[column.prop],
                          data,
                          column,
                          data['vue-tabulation_inner_index'],
                        )
                      : data[column.prop]
                  }}
                </div>
              </template>
            </td>
          </tr>
          <tr :style="{ height: bodyBottomPadding + 'px' }"></tr>
        </tbody>
      </table>
    </div>
    <!-- 合计行 -->
    <div v-if="showSummary" ref="summaryWrap" class="vue-tabulation__summary-wrapper">
      <!-- 主内容区域 -->
      <table ref="summary" class="vue-tabulation__summary" :style="gridHeaderStyle">
        <colgroup>
          <col
            v-for="(col, index) in fixedNormalizeColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
          <col v-if="hasScrollableY" class="vue-tabulation__gutter" :width="gutterWidth + 'px'" />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(col, index) in fixedNormalizeColumns"
              :key="index"
              :class="[computeCellClass(col)]"
            >
              {{ summaryData[col['vue-tabulation-index']] }}
            </th>
            <th v-if="hasScrollableY" class="vue-tabulation__gutter"></th>
          </tr>
        </thead>
      </table>

      <!-- 左固定区域 -->
      <table
        v-if="fixedLeftColumns.length"
        ref="summary-fixed-left"
        class="vue-tabulation__summary--fixed-left"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedLeftColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(item, index) in fixedLeftColumns"
              :key="index"
              :class="[computeCellClass(item)]"
            >
              {{ summaryData[item['vue-tabulation-index']] }}
            </th>
          </tr>
        </thead>
      </table>

      <!-- 右固定区域 -->
      <table
        v-if="fixedRightColumns.length"
        ref="summary-fixed-right"
        class="vue-tabulation__summary--fixed-right"
      >
        <colgroup>
          <col
            v-for="(col, index) in fixedRightColumns"
            :key="index"
            :width="
              columnsWidthMap[col.prop]
                ? columnsWidthMap[col.prop]
                : colPercent
                ? col.width + '%'
                : col.width + 'px'
            "
          />
          <col v-if="hasScrollableY" class="vue-tabulation__gutter" :width="gutterWidth + 'px'" />
        </colgroup>
        <thead>
          <tr>
            <th
              v-for="(item, index) in fixedRightColumns"
              :key="index"
              :class="[computeCellClass(item)]"
            >
              {{ summaryData[item['vue-tabulation-index']] }}
            </th>
            <th v-if="hasScrollableY" class="vue-tabulation__gutter"></th>
          </tr>
        </thead>
      </table>
    </div>
  </div>
</template>

<script>
import { sortFixed, deepClone, rAF, debug } from './util';
import scrollBarWidth from './scrollbar-width';
import HeaderCellSort from './headerCellSort';

const log = debug({
  prefix: 'qtTable:',
  disabled: true,
});

export default {
  name: 'qtTable',

  props: {
    dataSource: {
      type: Array,
      default() {
        return [];
      },
    },

    columns: {
      type: Array,
      required: true,
    },

    height: {
      type: Number,
    },

    border: {
      type: Boolean,
      default: true,
    },

    colPercent: {
      type: Boolean,
      default: false,
    },

    // 是否显示序号
    hasIndex: {
      type: Boolean,
      default: false,
    },

    // 是否显示复选框
    hasCheckbox: {
      type: Boolean,
      default: false,
    },

    // 设置每行的 class
    rowClassName: {
      type: Function,
    },

    // 是否显示合计列
    showSummary: {
      type: Boolean,
      default: false,
    },

    sumText: {
      type: String,
      default: '合计',
    },

    summaryMethod: {
      type: Function,
    },

    // 自定义索引
    indexMethod: {
      type: Function,
      default(index) {
        return index + 1;
      },
    },

    // 指定那个 prop 可以作为唯一值
    keyProp: {
      type: String,
    },

    // 点击某行是否选中该行的复选框
    rowClickCheck: {
      type: Boolean,
      default: true,
    },

    // 右键某行是否选中该行的复选框
    rowContextmenuCheck: {
      type: Boolean,
      default: true,
    },
  },

  data() {
    return {
      // 是否mounted
      isMounted: false,
      // 是否有垂直滚动条
      hasScrollableY: false,
      // 是有有水平滚动条
      hasScrollableX: false,
      gridBodyWrapHeight: undefined,
      // 每个列的宽度
      colsWidth: [],
      columnsWidthMap: {},
      // 列总宽度
      totalColWidth: 0,
      // 滚动条宽度
      gutterWidth: scrollBarWidth(),
      // 表格 header 水平滚动条的位置
      gridBodyScrollLeft: 0,
      gridBodyScrollTop: 0,
      normalizeDataSource: [],
      // 全部复选框是否勾选
      checkbox: false,

      // 可视区渲染
      // 每行的高度
      rowHeight: 40,
      // 行的数量
      rowsLength: 0,

      // 是否排序
      sortable: false, // false, true, 'custom'
      // 排序字段
      sortProp: 'age',
      // 排序方式，升序/降序
      sortOrder: 'ascending', // ascending, descending, 'none'
      sort: {
        prop: '',
        order: 'desc',
      },

      // 排序方法
      sortMethods: null,
      // 原始整体高度
      initTheWrapHeight: 0,
      initTheWrapWidth: 0,
    };
  },

  computed: {
    normalizeColumns() {
      const self = this;
      const result = this.columns.map((item) => {
        if (!item.headerCellComp && !item.headerCellRender && item.sortable) {
          item.headerCellComp = HeaderCellSort;
        }
        return item;
      });

      if (this.hasCheckbox) {
        result.unshift({
          label: '#',
          prop: 'vue-tabulation-checkbox',
          width: 50,
          align: 'center',
          headerCellRender(h) {
            return h('input', {
              domProps: {
                type: 'checkbox',
                checked: self.checkbox,
              },
              on: {
                change: (event) => {
                  self.$emit('grid-checkbox-toggle', event.target.checked);
                },
              },
            });
          },
          bodyCellRender(h, { value, row }) {
            return h('input', {
              domProps: {
                type: 'checkbox',
                checked: value,
              },
              on: {
                change: (event) => {
                  self.$emit(
                    'grid-checkbox-change',
                    event.target.checked,
                    row['vue-tabulation-index'] - 1,
                    row,
                  );
                },
              },
            });
          },
        });
      }

      if (this.hasIndex) {
        result.unshift({
          label: '序号',
          prop: 'vue-tabulation-index',
          width: 50,
          align: 'center',
        });
      }

      return result;
    },

    // 有固定列
    fixedNormalizeColumns() {
      let result = this.normalizeColumns.map((item) => {
        // 如果列定义的表头没有定义 headerCellComp 且 定义了 render 函数，返回这个 render 函数
        if (!item.headerCellComp && item.headerCellRender) {
          item.headerCellComp = this.headerRenderFactory(item.headerCellRender);
        }

        // 表内容
        if (!item.bodyCellComp && item.bodyCellRender) {
          item.bodyCellComp = this.bodyRenderFactory(item.bodyCellRender);
        }

        return item;
      });
      // 如果有左固定列，则把序号列和复选框列设置为左固定列
      if (this.normalizeColumns.some((item) => item.fixed === 'left' || item.fixed === true)) {
        result = this.normalizeColumns.map((item) => {
          if (item.prop === 'vue-tabulation-index' || item.prop === 'vue-tabulation-checkbox') {
            item.fixed = 'left';
          }
          return item;
        });
      }

      result = sortFixed(result).map((item, index) => {
        item['vue-tabulation-index'] = index;
        return item;
      });

      return result;
    },

    fixedLeftColumns() {
      return this.fixedNormalizeColumns.filter(
        (item) => item.fixed === 'left' || item.fixed === true,
      );
    },

    fixedRightColumns() {
      return this.fixedNormalizeColumns.filter((item) => item.fixed === 'right');
    },

    columnsLength() {
      return this.normalizeColumns.length;
    },

    gridClass() {
      return {
        'vue-tabulation--border': this.border,
        'vue-tabulation--scrollable-y': this.hasScrollableY,
      };
    },

    bodyWrapStyle() {
      return {
        height: this.gridBodyWrapHeight ? `${this.gridBodyWrapHeight}px` : undefined,
      };
    },

    gridHeaderStyle() {
      return {
        transform: `translateX(-${this.gridBodyScrollLeft}px)`,
      };
    },

    checkedRows() {
      return this.normalizeDataSource.filter((row) => row['vue-tabulation-checkbox']);
    },

    rowsHide() {
      if (this.rowHeight) {
        return Math.floor(this.gridBodyScrollTop / this.rowHeight);
      } else {
        return 0;
      }
    },

    rowShowSize() {
      if (this.rowHeight) {
        return Math.ceil(this.gridBodyWrapHeight / this.rowHeight);
      } else {
        return 0;
      }
    },

    rowRenderStart() {
      return this.rowsHide - 5 > 0 ? this.rowsHide - 5 : 0;
    },

    rowRenderEnd() {
      const value = this.rowRenderStart + this.rowShowSize + 10;
      return value > this.rowsLength ? this.rowsLength : value;
    },

    renderDataSource() {
      return this.normalizeDataSource.filter((item, index) => {
        return index >= this.rowRenderStart && index < this.rowRenderEnd;
      });
    },

    bodyTopPadding() {
      return this.rowRenderStart * this.rowHeight;
    },

    bodyBottomPadding() {
      return (this.rowsLength - this.rowRenderEnd) * this.rowHeight;
    },

    summaryData() {
      if (this.showSummary) {
        if (this.summaryMethod) {
          return this.summaryMethod({
            columns: this.fixedNormalizeColumns,
            data: this.normalizeDataSource,
          });
        } else {
          return this.defaultSummaryMethod({
            columns: this.fixedNormalizeColumns,
            data: this.normalizeDataSource,
          });
        }
      } else {
        return [];
      }
    },
  },

  watch: {
    height() {
      this.initHeight();
    },

    dataSource: {
      immediate: true,
      deep: true,
      handler() {
        this.$nextTick(() => {
          this.initHeight();
        });
        this.rowsLength = this.dataSource.length;
        this.normalizeDataSource = this.getNormalizeDataSource();
      },
    },

    fixedNormalizeColumns: {
      immediate: true,
      deep: true,
      handler() {
        this.$nextTick(() => {
          this.initColumnWidth();
        });
      },
    },

    sort: {
      deep: true,
      handler() {
        this.normalizeDataSource = this.getNormalizeDataSource();
      },
    },

    checkedRows: {
      immediate: true,
      handler() {
        if (this.checkedRows.length === this.rowsLength && this.rowsLength) {
          this.checkbox = true;
        } else {
          this.checkbox = false;
        }
      },
    },
  },

  methods: {
    getNormalizeDataSource() {
      const data = deepClone(this.dataSource);

      if (this.sort.prop && this.sort.order) {
        data.sort((a, b) => {
          const order = this.sort.order === 'asc' ? 1 : -1;
          let aValue = a[this.sort.prop];
          let bValue = b[this.sort.prop];
          if (typeof aValue === 'number' && typeof bValue === 'number') {
            return (aValue - bValue) * order;
          } else {
            aValue = aValue ? String(aValue) : '';
            bValue = bValue ? String(bValue) : '';
            return aValue.localeCompare(bValue) * order;
          }
        });
      }

      return data.map((item, index) => {
        item['vue-tabulation_inner_index'] = index;

        if (this.hasIndex) {
          item['vue-tabulation-index'] = this.indexMethod(index);
        }

        if (this.hasCheckbox) {
          item['vue-tabulation-checkbox'] = false;
        }

        return item;
      });
    },
    headerRenderFactory(headerCellRender) {
      const parent = this;

      return {
        name: 'table-header-cell',
        props: {
          label: {
            type: String,
            required: true,
          },
          index: {
            type: Number,
            required: true,
          },
          column: {
            type: Object,
            required: true,
          },
          sort: {
            type: Object,
            required: true,
          },
        },
        render(h) {
          const self = this;
          return headerCellRender.bind(self)(h, {
            label: this.label,
            index: this.index,
            column: this.column,
            sort: this.sort,
            parent,
          });
        },
      };
    },

    bodyRenderFactory(render) {
      const vm = this;
      return {
        name: 'cellBody',
        props: {
          value: {
            required: true,
          },
          prop: {
            type: String,
            required: true,
          },
          index: {
            type: Number,
            required: true,
          },
          column: {
            type: Object,
            required: true,
          },
          row: {
            type: Object,
            required: true,
          },
        },
        render(h) {
          return render.bind(vm)(h, {
            value: this.value,
            prop: this.prop,
            index: this.index,
            column: this.column,
            row: this.row,
          });
        },
      };
    },
    // 滚动时触发更新
    debounceScroll(e) {
      this.scrollLeft(e);
      this.scrollTop(e);
    },

    scrollLeft: rAF((e) => {
      this.gridBodyScrollLeft = e.target.scrollLeft;
    }),

    scrollTop: rAF((e) => {
      this.gridBodyScrollTop = e.target.scrollTop;
    }),

    // 点击行触发
    handleRowClick(data, event) {
      this.$emit('row-click', data['vue-tabulation_inner_index'], data, event);

      if (this.rowClickCheck) {
        this.selectionToggle([data['vue-tabulation_inner_index']], undefined, event);
      }
    },

    // 右键行触发
    handleContextmenu(data, event) {
      this.$emit('row-contextmenu', data['vue-tabulation_inner_index'], data, event);

      if (this.rowContextmenuCheck) {
        this.selectionToggle([data['vue-tabulation_inner_index']], true, event);
      }
    },

    // 多选操作
    selectionAllToggle(value) {
      log('selectionAllToggle');

      this.normalizeDataSource = this.normalizeDataSource.map((item) => {
        item['vue-tabulation-checkbox'] =
          value === undefined ? !item['vue-tabulation-checkbox'] : Boolean(value);
        return item;
      });

      this.$emit('select-all', this.checkedRows);
      this.$emit('selection-change', this.checkedRows);
    },
    /**
     * 切换指定行的复选框选中状态
     * @param {array|number} rowIndexArr
     * @param {boolean} [checked]
     */
    selectionToggle(rowIndexArr, checked, event) {
      rowIndexArr = Array.isArray(rowIndexArr) ? rowIndexArr : [rowIndexArr];

      this.normalizeDataSource = this.normalizeDataSource.map((item, index) => {
        if (rowIndexArr.includes(index)) {
          item['vue-tabulation-checkbox'] =
            checked === undefined ? !item['vue-tabulation-checkbox'] : Boolean(checked);
        }
        return item;
      });

      this.$emit('select', this.checkedRows, rowIndexArr[0], event);
      this.$emit('selection-change', this.checkedRows);
    },
    selectionChange(value, rowIndex) {
      if (!this.rowClickCheck) {
        this.selectionToggle([rowIndex], value);
      }
    },
    // 计算单元格样式
    computeCellClass(column) {
      const classObj = {};

      if (['left', 'center', 'right'].includes(column.align)) {
        classObj[`is-${column.align}`] = true;
      }

      return classObj;
    },
    // 默认合计行方法
    defaultSummaryMethod({ columns, data }) {
      const isAllNumber = (arr) => {
        if (!arr.length) return false;
        return arr.every((item) => typeof item !== 'boolean' && !Number.isNaN(Number(item)));
      };

      return columns.map((item, index) => {
        const values = data.map((itemData) => itemData[item.prop]);

        if (index === 0) {
          return this.sumText;
        } else if (isAllNumber(values)) {
          return values.reduce((prev, curr) => {
            return prev + Number(curr);
          }, 0);
        } else {
          return '';
        }
      });
    },
    // 计算内容的高度，是否有滚动条
    initHeight() {
      if (!this.isMounted || !this.columns.length) return false;

      log('initHeight');

      let initsummaryWrapHeight = 0;
      if (this.showSummary) {
        initsummaryWrapHeight = this.$refs.summaryWrap.offsetHeight;
      }

      if (this.height) {
        this.gridBodyWrapHeight = this.height - this.initHeaderWrapHeight - initsummaryWrapHeight;
        // 如果设置的高度小于表格本身的高度则会产生垂直滚动条并且表头固定
        if (this.height < this.$refs.body.offsetHeight) {
          this.hasScrollableY = true;
        } else {
          this.hasScrollableY = false;
        }
      } else {
        this.gridBodyWrapHeight = this.initBodyWrapHeight;
        this.hasScrollableY = false;
      }

      log('hasScrollableY', this.hasScrollableY);

      this.initRowHeight();
    },

    /**
     * 计算每列的宽度
     * 计算列的总宽度
     * 计算列的高度
     * 计算gutter的高度
     */
    initColumnWidth() {
      if (!this.isMounted || !this.columns.length) return false;

      const bodyCells = this.$refs.headerCell || [];

      this.fixedNormalizeColumns.forEach((column, index) => {
        const width = bodyCells[index] && bodyCells[index].offsetWidth;
        this.columnsWidthMap[column.prop] = width;
      });

      this.getTotalColWidth();
    },

    getTotalColWidth() {
      this.totalColWidth = Object.keys(this.columnsWidthMap).reduce(
        (acc, key) => acc + this.columnsWidthMap[key],
        0,
      );
    },

    // 获取每行的高度
    initRowHeight() {
      const row = this.$refs.body.querySelector('.vue-tabulation-body-tr');

      if (row) {
        this.rowHeight = row.offsetHeight;
      }
    },

    changeSize(prop, newWidth) {
      const oldWith = this.columnsWidthMap[prop];

      // 如果改变宽度后，列的总宽度仍大于表格总宽度，只改变拖拽列的宽度
      // console.log(this.totalColWidth - (oldWith - newWidth) - this.initTheWrapWidth)
      if (this.totalColWidth - (oldWith - newWidth) >= this.initTheWrapWidth) {
        this.columnsWidthMap = { ...this.columnsWidthMap, [prop]: newWidth };
      } else {
        // 存储改变宽度列之后的列
        const lastKeys = [];
        let idx = 0;

        Object.keys(this.columnsWidthMap).forEach((key, index) => {
          if (key === prop) {
            idx = index;
          }
          if (idx && index > idx) {
            lastKeys.push(key);
          }
        });

        log('lastKeys', lastKeys, 'diffWidth', oldWith - newWidth);

        if (lastKeys.length) {
          let diffWidth = oldWith - newWidth;
          if (diffWidth > 0) {
            diffWidth /= lastKeys.length;
            lastKeys.forEach((key) => {
              const oldW = this.columnsWidthMap[key];
              this.columnsWidthMap[key] = oldW + diffWidth;
            });

            this.columnsWidthMap = deepClone(this.columnsWidthMap);
          }
        }
      }
      this.getTotalColWidth();
    },
  },

  created() {
    log('created');
    // 复选框全选或者取消全选
    this.$on('grid-checkbox-toggle', this.selectionAllToggle);
    // 复选框改变
    this.$on('grid-checkbox-change', this.selectionChange);
  },

  mounted() {
    log('mounted-start');

    this.isMounted = true;
    // 表格bodyWrap的初始的初始高度
    this.initBodyWrapHeight = this.$refs.bodyWrap.offsetHeight;
    this.initTheWrapHeight = this.$refs.theWrap.offsetHeight;
    this.initTheWrapWidth = this.$refs.theWrap.offsetWidth;
    this.initHeaderWrapHeight = this.$refs.headerWrap.offsetHeight;
    this.initHeight();
    this.initColumnWidth();

    log('mounted-end');
  },

  activated() {
    // 修复使用`keep-alive`时，切换路由，滚动条重置为之前的状态
    this.$refs.bodyWrap.scrollTop = this.gridBodyScrollTop;
    this.$refs.bodyWrap.scrollLeft = this.gridBodyScrollLeft;
  },

  updated() {
    log('updated');
  },
};
</script>

<style lang="scss" src="./style.scss"></style>
